package org.restlesscode.javersion;

import java.io.*;
import java.util.HashMap;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNPropertyValue;

/**
 * Reads java objects from an SVN repository.
 * Probably not thread safe yet.
 */
public class SvnObjectReader {

	private static final Log LOG = LogFactory.getLog(SvnObjectReader.class);
	
	protected SvnStore svnStore;

    /**
     * Create an SVN Reader by pointing it at an SvnStore.
     * @param svnStore pointer at svn repository storing objects
     */
	public SvnObjectReader(SvnStore svnStore) {
		this.svnStore = svnStore;
	}

    /**
     * Read the latest version of an object from the SVN repository for this
     * reader using the basic transcoder.
     * @param path Path of object in repository
     * @return HEAD object from repository
     * @throws IOException
     * @throws MissingObjectException
     */
    public Object read(String path) throws IOException, MissingObjectException {
        return read(path, SvnRevision.HEAD);
    }

    /**
     * Read the specified revision from the SVN repository.
     * @param path Path of object in repository
     * @param revision revision to read
     * @return object from repository
     * @throws IOException
     * @throws MissingObjectException
     */
    public Object read(String path, long revision) throws IOException, MissingObjectException {
        return read(path, revision, svnStore.getDefaultTranscoder());
    }

	/**
	 * Reads and returns a java object from a subversion repository.
	 * @param path Path from root of SVN repository of object
	 * @param revision Revision number to load, -1 for HEAD
	 * @param transcoder transcoder to use to decode object
	 * @return Object constructed from repo
	 * @throws IOException If object isn't found or if there was an error during serialization
	 * @throws MissingObjectException 
	 */
	public <T> T read(String path, long revision, Transcoder<T> transcoder) throws IOException, MissingObjectException {
	
		SVNProperties fileProperties = new SVNProperties();
    	ByteArrayOutputStream baos = new ByteArrayOutputStream();

        SvnObject svnObject = new SvnObject();
        svnObject.properties = new HashMap<String, SvnPropertyObject>();

        try {
            svnStore.repository.getFile(path, revision, fileProperties, baos);

            Set propertyNames = fileProperties.nameSet();

            for (Object propertyName : propertyNames) {
                SVNPropertyValue svnPropertyValue = fileProperties.getSVNPropertyValue((String) propertyName);
                if (svnPropertyValue.isBinary()) {
                    byte []val = svnPropertyValue.getBytes();
                    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(val));
                    Serializable storedObj = (Serializable) ois.readObject();
                    svnObject.properties.put((String) propertyName,
                        new SvnPropertyObject(storedObj, SvnPropertyObject.PropertyType.OBJECT));
                } else {
                    String val = svnPropertyValue.getString();
                    svnObject.properties.put((String) propertyName,
                        new SvnPropertyObject(val, SvnPropertyObject.PropertyType.STRING));
                }
            }

            svnObject.version = svnObject.getProperty(
                    SvnObjectWriter.VERSION_FIELD).getValueAsString();
            svnObject.transcoderName = svnObject.getProperty(
                    SvnObjectWriter.TRANSCODER_NAME_FIELD).getValueAsString();

            svnObject.content = baos.toByteArray();
            return transcoder.decode(svnObject);

        } catch (SVNException e) {
    		// Could do a checkPath call to SVN before this instead of this fuzzier check for
    		// a missing object, but figured I'd minimize calls to SVN when possible
    		if (e.getMessage().contains("path not found")) {
    			throw new MissingObjectException();
    		}
    		throw new IOException(e);
    	} catch (SecurityException e) {
			throw new IOException(e);
		} catch (IllegalArgumentException e) {
			throw new IOException(e);
		} catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
    }
	
}
